﻿using System;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using SCG = System.Collections.Generic;

using Nemerle.Assertions;
using Nemerle.Utility;

using GASS.OpenCL;
using GASS.OpenCL.OpenCLDriver;

namespace Nemerle.OpenCL {

    /// <summary>
    /// Description of Kernel.
    /// </summary>
    [InfoProperty(CLKernelInfo.ReferenceCount, int, Flags(no_cache))]
    [InfoProperty(CLKernelInfo.FunctionName, string)]
    [EqualityByIdPattern]
    public sealed class Kernel /* : IDisposable */ {

        internal this([NotNull] program : Program, kernel_id : CLKernel) {
            this.program = program;
            this.handle = KernelHandle(kernel_id);
            this.arguments = KernelArguments(this);
            this.workgroup_infos = {
                def infos = SCG.List();
                foreach(device in program.Context.Devices)
                    infos.Add(KernelWorkgroupInfo(this, device));
                ReadOnlyCollection(infos)
            }
        }

        [Accessor] program : Program;

        [Accessor] arguments : KernelArguments;

        [Accessor] workgroup_infos : ReadOnlyCollection[KernelWorkgroupInfo];

        handle : KernelHandle;

        public Id : CLKernel {
            get { handle.EntityId }
        }

        public WorkgroupInfo : KernelWorkgroupInfo {
            get { workgroup_infos[0] }
        }

        public GetWorkgroupInfo(device : Device) : KernelWorkgroupInfo {
            foreach(info when info.Device.Equals(device) in workgroup_infos)
                Nemerle.Imperative.Return(info);
            null
        }

        public override ToString() : string {
            def sb = Text.StringBuilder("kernel ").Append(FunctionName).Append(" (");
            def args_count = arguments.Count;
            for(mutable no = 0; no < args_count; ++no) {
                when(no > 0) _ = sb.Append(", ");
                _ = sb.Append("p").Append(no);
            }
            sb.Append(")").ToString()
        }

        public static NDRange(x : long) : NDRange {
            array[x :> SizeT]
        }

        public static NDRange(x : long, y : long) : NDRange {
            array[x :> SizeT, y :> SizeT]
        }

        public static NDRange(x : long, y : long, z : long) : NDRange {
            array[x :> SizeT, y :> SizeT, z :> SizeT]
        }

        #region IDisposable

        internal Dispose() : void {
            when(null != handle)
                handle.Close();
        }

        #endregion

        [InfoProperty(CLKernelWorkGroupInfo.LocalMemSize, ulong, Args(device.Id))]
        [InfoProperty(CLKernelWorkGroupInfo.WorkGroupSize, long, Args(device.Id), Flags(size_t, no_cache))]
        [InfoProperty(CLKernelWorkGroupInfo.CompileWithWorkGroupSize, array[SizeT], Args(device.Id))]
        public sealed class KernelWorkgroupInfo {

            internal this(kernel : Kernel, device : Device) {
                this.Id = kernel.Id;
                this.device = device;
            }

            Id : CLKernel;

            [Accessor] device : Device;

        }

        [InfoProperty(CLKernelInfo.NumArgs, int, Name = Count)]
        public sealed class KernelArguments {

            internal this(kernel : Kernel) {
                this.Id = kernel.Id;
            }

            Id : CLKernel;

            args : LazyValue[array[KernelArgument]] = lazy(array(Count));

            public Item[index : int] : KernelArgument {
                get {
                    def args = args.Value;
                    match(args[index]) {
                        | null =>
                            def arg = KernelArgument.Null();
                            args[index] = arg;
                            arg
                        | arg => arg
                    }
                }
                set {
                    def value = value ?? KernelArgument.Null();
                    match(value) {
                        | Null =>
                            chk_err clSetKernelArg(Id, index :> uint, 0, IntPtr.Zero)

                        | Size(size) =>
                            chk_err clSetKernelArg(Id, index :> uint, size, IntPtr.Zero)

                        | Array(buffer, size) =>
                            fixed(ptr = buffer)
                                chk_err clSetKernelArg(Id, index :> uint, size, ptr)

                        | Memory(mem) =>
                            fixed(id_ptr = mem.Id)
                                chk_err clSetKernelArg(Id, index :> uint, Marshal.SizeOf(typeof(CLMem)) :> uint, id_ptr)

                        | Sampler(sampler) =>
                            fixed(id_ptr = sampler.Id)
                                chk_err clSetKernelArg(Id, index :> uint, Marshal.SizeOf(typeof(CLSampler)) :> uint, id_ptr)

                        | Data(obj, size) =>
                            fixed(obj_ptr = obj)
                                chk_err clSetKernelArg(Id, index :> uint, size, obj_ptr)
                    }
                    args.Value[index] = value;
                }
            }

        }

    }

    public variant KernelArgument {
        | Null
        | Size {
            size : uint;
        }
        | Array {
            buffer : System.Array;
            size : uint;
        }
        | Memory {
            mem : Nemerle.OpenCL.Memory;
        }
        | Sampler {
            sampler : Nemerle.OpenCL.Sampler;
        }
        | Data {
            obj : object;
            size : uint;
        }

        public IsNull : bool {
            get {
                match(this) {
                    | Null => true
                    | _ => false
                }
            }
        }

        public static @: [T](a : array[T]) : KernelArgument {
            Array(a, Utils.ArrayHelper.GetArraySize.[T](a))
        }

        public static @: [T](a : array[2, T]) : KernelArgument {
            Array(a, Utils.ArrayHelper.GetArraySize.[T](a))
        }

        public static @: [T](a : array[3, T]) : KernelArgument {
            Array(a, Utils.ArrayHelper.GetArraySize.[T](a))
        }

        public static @: (mem : Nemerle.OpenCL.Memory) : KernelArgument {
            KernelArgument.Memory(mem)
        }

        public static @: (buffer : Nemerle.OpenCL.Buffer) : KernelArgument {
            KernelArgument.Memory(buffer)
        }

        public static @: (image : Nemerle.OpenCL.Image2D) : KernelArgument {
            KernelArgument.Memory(image)
        }

        public static @: (image : Nemerle.OpenCL.Image3D) : KernelArgument {
            KernelArgument.Memory(image)
        }

        public static @: (sampler : Nemerle.OpenCL.Sampler) : KernelArgument {
            KernelArgument.Sampler(sampler)
        }

        public static @: [T](obj : T) : KernelArgument where T : struct {
            Data(obj, Marshal.SizeOf(typeof(T)) :> uint)
        }

    }

}
